package com.hzc.springchan.util;

import com.hzc.springchan.constant.System;
import org.springframework.data.redis.core.*;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 * @author chen990627
 * @date 2022/10/16
 */
public class RedisUtils {

    private RedisUtils() {
    }

    private static RedisUtils instance;
    private static RedisTemplate customRedisTemplate;
    private static RedisTemplate stringRedisTemplate;
    private static StringOperation stringOperation;
    private static ListOperation listOperation;
    private static MapOperation mapOperation;
    private static SetOperation setOperation;

    static {
        instance = new RedisUtils();
        customRedisTemplate = SpringUtils.getBean("customRedisTemplate", RedisTemplate.class);
        stringRedisTemplate = SpringUtils.getBean("stringRedisTemplate", RedisTemplate.class);

        stringOperation = instance.new StringOperation();
        listOperation = instance.new ListOperation();
        mapOperation = instance.new MapOperation();
        setOperation = instance.new SetOperation();
    }


    abstract class Operation<T> {

        /**
         * redis key前缀
         *
         * @return
         */
        abstract String prefix();

        /**
         * 设值
         *
         * @param key
         * @param value
         * @param expire
         * @return
         */
        abstract boolean setValue(String key, T value, long expire);

        /**
         * 取值
         *
         * @param key
         * @param <T>
         * @return
         */
        abstract <T> T getValue(String key);
    }

    /**
     * String value
     */
    class StringOperation extends Operation<String> {

        private ValueOperations valueOperations;

        {
            valueOperations = stringRedisTemplate.opsForValue();
        }

        @Override
        String prefix() {
            return System.RedisKey.KEY_STRING;
        }

        @Override
        boolean setValue(String key, String value, long expire) {

            try {
                valueOperations.set(key, value, expire, TimeUnit.SECONDS);
            } catch (Exception e) {
                LogUtils.error("redis setStringValue error: {}", e);
                return false;
            }
            return true;
        }

        @Override
        String getValue(String key) {

            try {
                return (String) valueOperations.get(key);
            } catch (Exception e) {
                LogUtils.error("redis getStringValue error: {}", e);
            }
            return null;
        }

        public boolean tryLock(String key, long expire) {
            //如果key存在，插入值失败，返回false，可以循环执行，直至成功或超时。
            //如果key不存，插入值成功，返回true，业务逻辑处理结束后，将key删除。

            if (valueOperations.setIfAbsent(key, "ok")) {
                valueOperations.set(key, "ok", expire, TimeUnit.SECONDS);
                return true;
            }

            long ttl = stringRedisTemplate.getExpire(key);
            if (ttl == -1) {
                synchronized (this) {
                    delValue(key);
                    if (valueOperations.setIfAbsent(key, "ok")) {
                        valueOperations.set(key, "ok", expire, TimeUnit.SECONDS);
                        return true;
                    }
                }
            }

            return false;
        }

        public void unLock(String key) {
            delValue(key);
        }

        public void delValue(String key) {
            valueOperations.set(key, "null", 1, TimeUnit.MILLISECONDS);
        }
    }

    /**
     * List value
     */
    class ListOperation extends Operation<List> {

        private ListOperations<String, List> listOperations;

        {
            listOperations = customRedisTemplate.opsForList();
        }

        @Override
        String prefix() {
            return System.RedisKey.KEY_LIST;
        }

        @Override
        boolean setValue(String key, List value, long expire) {
            try {
                listOperations.leftPush(key, value);
                customRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
            } catch (Exception e) {
                LogUtils.error("redis setListValue error: {}", e);
                return false;
            }
            return true;
        }

        @Override
        List getValue(String key) {
            try {
                return listOperations.leftPop(key);
            } catch (Exception e) {
                LogUtils.error("redis getListValue error: {}", e);
            }
            return null;
        }
    }

    /**
     * Map value
     */
    class MapOperation extends Operation<Map<String, Object>> {

        private HashOperations<String, String, Object> hashOperations;

        {
            hashOperations = customRedisTemplate.opsForHash();
        }

        @Override
        String prefix() {
            return System.RedisKey.KEY_MAP;
        }

        @Override
        boolean setValue(String key, Map<String, Object> value, long expire) {

            try {
                for (Map.Entry<String, Object> entry : value.entrySet()) {
                    hashOperations.put(key, entry.getKey(), entry.getValue());
                }
                customRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
            } catch (Exception e) {
                LogUtils.error("redis setMapValue error: {}", e);
                return false;
            }
            return true;
        }

        @Override
        Map<String, Object> getValue(String key) {

            try {
                return hashOperations.entries(key);
            } catch (Exception e) {
                LogUtils.error("redis getMapValue error: {}", e);
            }
            return null;
        }
    }

    /**
     * Set value
     */
    class SetOperation extends Operation<Set<Object>> {

        private SetOperations<String, Object> setOperations;

        {
            setOperations = customRedisTemplate.opsForSet();
        }

        @Override
        String prefix() {
            return System.RedisKey.KEY_SET;
        }

        @Override
        boolean setValue(String key, Set<Object> value, long expire) {

            try {
                setOperations.add(key, value);
                customRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
            } catch (Exception e) {
                LogUtils.error("redis setSetValue error: {}", e);
                return false;
            }
            return true;
        }

        @Override
        Set<Object> getValue(String key) {

            try {
                return setOperations.members(key);
            } catch (Exception e) {
                LogUtils.error("redis getSetValue error: {}", e);
            }
            return null;
        }
    }

    public static boolean setString(String key, String value, long expire) {
        return setValue(stringOperation, key, value, expire);
    }

    public static boolean setList(String key, List value, long expire) {
        return setValue(listOperation, key, value, expire);
    }

    public static boolean setMap(String key, Map<String, Object> value, long expire) {
        return setValue(mapOperation, key, value, expire);
    }

    public static boolean setSet(String key, Set<Object> value, long expire) {
        return setValue(setOperation, key, value, expire);
    }

    public static String getString(String key) {
        return getValue(stringOperation, key);
    }

    public static List getList(String key) {
        return getValue(listOperation, key);
    }

    public static Map getMap(String key) {
        return getValue(mapOperation, key);
    }

    public static Set getSet(String key) {
        return getValue(setOperation, key);
    }

    public static boolean setLock(String key, long expire) {
        return stringOperation.tryLock(generateKey(stringOperation, key), expire);
    }

    public static void delString(String key) {
        stringOperation.delValue(generateKey(stringOperation, key));
    }

    private static <T> boolean setValue(Operation<T> operation, String key, T value, long expire) {
        return operation.setValue(generateKey(operation, key), value, expire);
    }

    private static <T> T getValue(Operation<T> operation, String key) {
        return operation.getValue(generateKey(operation, key));
    }

    private static String generateKey(Operation operation, String key) {
        return operation.prefix() + key;
    }
}
